---
description:
  Yoga automatically masks unexpected errors and prevents leaking sensitive information to clients.
---

# Error Masking

Yoga automatically masks unexpected errors and prevents leaking sensitive information to clients.

Unexpected errors can be caused by failed connections to remote services such as databases or HTTP
APIs. Nobody external needs to know that your database server is not reachable. Exposing such
information to the outside world can make you vulnerable for targeted attacks.

In order to build secure applications, it is crucial to understand this concept.

## Getting started

Let's setup a simple schema that calls a remote service that is unavailable.

```ts filename="Error Masking Example"
import { createSchema, createYoga } from 'graphql-yoga'
import { fetch } from '@whatwg-node/fetch'

// Provide your schema
const yoga = createYoga({
  schema: createSchema({
    typeDefs: /* GraphQL */ `
      type Query {
        greeting: String!
      }
    `,
    resolvers: {
      Query: {
        greeting: async () => {
          // This service does not exist
          const greeting = await fetch('http://localhost:9876/greeting').then(res => res.text())

          return greeting
        }
      }
    }
  })
})

// Start the server and explore http://localhost:4000/graphql
const server = createServer(yoga)
server.listen(4000, () => {
  console.info('Server is running on http://localhost:4000/graphql')
})
```

Executing the following operation results in an execution result that does not include any detail
from the error raised by the fetch call.

```graphql filename="GraphQL Operation"
query {
  greeting
}
```

```json filename="Execution Result with leaking error message"
{
  "errors": [
    {
      "message": "Unexpected error.",
      "locations": [
        {
          "line": 2,
          "column": 3
        }
      ],
      "path": ["greeting"]
    }
  ],
  "data": null
}
```

As you can see Yoga comes with sensible defaults as error masking is enabled without you needing to
configure anything.

## Receive original error in development

When developing locally seeing the original error within your Chrome Dev Tools might be handy for
debugging. You might be tempted to disable the masked errors via the `maskedErrors` config option,
however, **we do not recommend that at all**.

Having development and production behavior as close as possible is very important for not having any
surprises in production. Instead, we recommend enabling the Yoga development mode.

To do this you need to start Yoga with the `NODE_ENV` environment variable set to `"development"`.

On unix and windows systems the environment variable can be set when starting the server.

```bash filename="Linux/MacOS"
NODE_ENV=development node server.js
```

```bash filename="Windows"
set NODE_ENV=development
node server.js
```

```graphql filename="GraphQL Operation"
query {
  greeting
}
```

This will add a more detailed error with a proper stacktrace to the errors extensions.

```json filename="GraphQL Error Response with original error extensions"
{
  "errors": [
    {
      "message": "Unexpected error.",
      "locations": [
        {
          "line": 2,
          "column": 3
        }
      ],
      "path": ["greeting"],
      "extensions": {
        "originalError": {
          "message": "request to http://localhost:9876/greeting failed, reason: connect ECONNREFUSED 127.0.0.1:9876",
          "stack": "FetchError: request to http://localhost:9876/greeting failed, reason: connect ECONNREFUSED 127.0.0.1:9876\n    at ClientRequest.<anonymous> (C:\\Users\\XXXX\\Projects\\graphql-yoga\\node_modules\\node-fetch\\lib\\index.js:1483:11)\n    at ClientRequest.emit (events.js:376:20)\n    at Socket.socketErrorListener (_http_client.js:475:9)\n    at Socket.emit (events.js:376:20)\n    at emitErrorNT (internal/streams/destroy.js:106:8)\n    at emitErrorCloseNT (internal/streams/destroy.js:74:3)\n    at processTicksAndRejections (internal/process/task_queues.js:82:21)"
        }
      }
    }
  ],
  "data": null
}
```

If you use this environment variable but you want to disable this feature explicitly, you can
configure it inside `createYoga` directly.

```ts
createYoga({
  maskedErrors: {
    isDev: false // This will prevent the masking to add `originalError` to `extensions` even if `NODE_ENV` is set to `development`
  }
})
```

## Exposing expected errors

Sometimes it is feasible to throw errors within your GraphQL resolvers whose message should be sent
to clients instead of being masked. This can be achieved by throwing a `GraphQLError` instead of a
"normal"
[`Error`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error).

E.g. you might want to throw an error if a resource cannot be found by an ID.

```ts filename="Error thrown for non extisting user" {26-28}
import { GraphQLError } from 'graphql'
import { createSchema, createYoga } from 'graphql-yoga'

const users = [
  { id: '1', login: 'Laurin' },
  { id: '2', login: 'Saihaj' },
  { id: '3', login: 'Dotan' }
]

// Provide your schema
const yoga = createYoga({
  schema: createSchema({
    typeDefs: /* GraphQL */ `
      type User {
        id: ID!
        login: String!
      }
      type Query {
        user(byId: ID!): User!
      }
    `,
    resolvers: {
      Query: {
        user: async (_, args) => {
          const user = users.find(user => user.id === args.byId)
          if (!user) {
            throw new GraphQLError(`User with id '${args.byId}' not found.`)
          }

          return user
        }
      }
    }
  })
})

// Start the server and explore http://localhost:4000/graphql
const server = createServer(yoga)
server.listen(4000, () => {
  console.info('Server is running on http://localhost:4000/graphql')
})
```

```graphql filename="Query for non existing user"
query {
  user(byId: "6") {
    id
  }
}
```

```json filename="Execution result with error message" {4}
{
  "errors": [
    {
      "message": "User with id '6' not found.",
      "locations": [
        {
          "line": 2,
          "column": 3
        }
      ],
      "path": ["user"]
    }
  ],
  "data": null
}
```

## Error codes and other extensions

Sometimes it is useful to enrich errors with additional information, such as an error code that can
be interpreted by the client.

Error extensions can be passed as the second parameter to the `GraphQLError` constructor.

```ts
import { GraphQLError } from 'graphql'
import { createSchema, createServer } from 'graphql-yoga'

const users = [
  { id: '1', login: 'Laurin' },
  { id: '2', login: 'Saihaj' },
  { id: '3', login: 'Dotan' }
]

// Provide your schema
const yoga = createYoga({
  schema: createSchema({
    typeDefs: /* GraphQL */ `
      type User {
        id: ID!
        login: String!
      }
      type Query {
        user(byId: ID!): User!
      }
    `,
    resolvers: {
      Query: {
        user: async (_, args) => {
          const user = users.find(user => user.id === args.byId)
          if (!user) {
            throw new GraphQLError(
              `User with id '${args.byId}' not found.`,
              // error extensions
              {
                extensions: {
                  code: 'USER_NOT_FOUND'
                }
              }
            )
          }

          return user
        }
      }
    }
  })
})

// Start the server and explore http://localhost:4000/graphql
const server = createServer(yoga)
server.listen(4000, () => {
  console.info('Server is running on http://localhost:4000/graphql')
})
```

**Query for non existing user**

```graphql
query {
  user(byId: "6") {
    id
  }
}
```

**Execution result with error message**

```json
{
  "errors": [
    {
      "message": "User with id '6' not found.",
      "locations": [
        {
          "line": 2,
          "column": 3
        }
      ],
      "path": ["user"],
      "extensions": {
        "code": "USER_NOT_FOUND"
      }
    }
  ],
  "data": null
}
```

The extensions are not only limited to a `code` property. Any JSON serializable value can be passed
as extensions.

```ts filename="GraphQLError with a variety of extensions" {5-13}
throw new GraphQLError(
  `User with id '${args.byId}' not found.`,
  // error extensions
  {
    extensions: {
      code: 'USER_NOT_FOUND',
      userId: args.byId,
      foo: {
        some: {
          complex: ['structure']
        }
      }
    }
  }
)
```

### Modifying HTTP status codes and headers

The `http` `extensions` field, has special handling within GraphQL Yoga. It will always be used for
determining what status code and additional headers should be sent to a client. The field itself
will never be included with the response extensions sent to the user.

The default status code of 200 can be overwritten by specifying the `extensions.http.status` field.
In case of multiple errors that try to alter the status code, the highest status code will be used.
E.g. if the first error has a status code of 404 and the second error has a status code of 400, the
status code of the first error will be used.

You can add additional HTTP headers by specifying the `extensions.http.headers` field. All the
headers specified in that map will be sent to the client. In case multiple errors modify the headers
the last error raised during the GraphQL execution phase will overwrite the previous ones.

**Note:** Setting the status code and headers is only possible for the initial payload when sending
a multipart operation (defer/stream) and only possible when subscribing to the event source in a
subscription operation (the HTTP status of an ongoing request that already sent a status code cannot
be changed). Please use `extensions.http` with caution.

```ts filename="GraphQL Error with http extensions." {6-11}
throw new GraphQLError(
  `User with id '${args.byId}' not found.`,
  // error extensions
  {
    extensions: {
      http: {
        status: 400,
        headers: {
          'x-custom-header': 'some-value'
        }
      }
    }
  }
)
```

## Disabling error masking

We highly recommend using error masking. However, you can still disable it using the `maskedErrors`
config option.

```ts filename="Disable Error Masking"
createYoga({ maskedErrors: false })
```

Executing the following operation will now result in leaking an error message that exposes
information about internal API calls.

```graphql filename="GraphQL Operation"
query {
  greeting
}
```

```json filename="Execution Result with leaking error message"
{
  "errors": [
    {
      "message": "request to http://localhost:9876/greeting failed, reason: connect ECONNREFUSED 127.0.0.1:9876",
      "locations": [
        {
          "line": 2,
          "column": 3
        }
      ],
      "path": ["greeting"]
    }
  ],
  "data": null
}
```

## Customize error masking

For advanced use-cases e.g. when using GraphQL Yoga as a Gateway, you might want to automatically
expose errors that are resolved from the subgraphs as you asume these already take care of hiding
sensitive information. In this case, you can customize the error masking behavior by providing a
custom `maskError` function.

Use the default `maskError` function as a fallback for all other errors.

```ts filename="Custom Mask Error Usage"
import { createYoga, maskError } from 'graphql-yoga'
import { schema } from './schema.js'

const yoga = createYoga({
  schema,
  maskedErrors: {
    maskError(error, message, isDev) {
      if (error?.extensions?.code === 'DOWNSTREAM_SERVICE_ERROR') {
        return error
      }

      return maskError(error, message, isDev)
    }
  }
})
```
